﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Dynamics.BusinessConnectorNet;
using System.Globalization;
using System.Collections;
using PJ.Framework;
using PJ.Framework.DynamicsAX;

namespace PJ.Data
{
    /// <summary>
    /// Dynamics AX server implementation of <see cref="IObjectDataProvider"> persistence interface. 
    /// </summary>
    /// <typeparam name="TDataInterface">Object data interface type</typeparam>
    /// <typeparam name="TDataConcreteClass">Object data concrete class type</typeparam>
    public abstract class ObjectAXProvider<TIObjectData> : IObjectProvider<TIObjectData>
        where TIObjectData : class, IObjectData
    {
        #region Constructors

        protected ObjectAXProvider(IObjectDataCache<TIObjectData> cache)
        {
            if (cache == null)
                throw new ArgumentNullException("cache");
            _Cache = cache;
        }
        #endregion

        #region Interface implementation

        public abstract TIObjectData CreateNewItem();

        public virtual bool Exist(string globalId)
        {
            throw new NotImplementedException();
        }

        public virtual void Delete(string globalId)
        {
            throw new NotImplementedException();
        }

        public virtual TIObjectData GetData(string globalId, GetDataMode mode)
        {
            TIObjectData data = (Cache == null) ? null : Cache.Find(globalId);
            if (data == null)
            {
                int tableId;
                Int64 recordId;

                GetAxRecordInfo(globalId, out tableId, out recordId);
                using (AxaptaContainer container = (AxaptaContainer)CallAXStaticMethod("Find", tableId, recordId))
                {
                    IEnumerator enumerator = container.GetEnumerator();
                    if (enumerator.MoveNext())
                    {
                        data = CreateNewItem(enumerator);
                        if (enumerator.MoveNext())
                            throw new ArgumentException("Container size error", "container");
                        if (Cache != null)
                            Cache.Add(data);
                        return data;
                    }
                    else
                    {
                        if ((mode & GetDataMode.Find) == GetDataMode.Find)
                            return null;
                        else
                            throw new Data.DataNotFoundException("globalId", globalId);
                    }
                }
            }
            else
                return data;
        }


        public virtual List<TIObjectData> GetList(string[] globalIds, GetDataMode mode)
        {
            if (globalIds == null || globalIds.Count() == 0)
                throw new ArgumentNullException("globalIds");

            Dictionary<string, TIObjectData> inStorage = null;
            Dictionary<string, TIObjectData> inCache = null;    // List of objects found in cache
            string[] toGetIds;                                  // List of ids to fetch from storage

            if ((mode & GetDataMode.UseCache) == GetDataMode.UseCache && Cache != null) // Use cache if UseCache mode and cache exists
                inCache = Cache.Find(globalIds, out toGetIds);
            else
                toGetIds = globalIds;
            if (toGetIds != null && toGetIds.Count() != 0) // Some ids are not found in cache, get them from storage
            {
                int tableId = 0;
                Int64 recordId;

                using (AxaptaContainer container = DynamicsAXBroker.CreateContainer())
                {
                    foreach (string globalId in toGetIds)
                    {
                        GetAxRecordInfo(globalId, out tableId, out recordId);
                        container.Add(recordId);
                    }
                    using (AxaptaContainer result = (AxaptaContainer)CallAXStaticMethod("FindList", tableId, container))
                    {
                        inStorage = new Dictionary<string, TIObjectData>();
                        IEnumerator enumerator = result.GetEnumerator();
                        while (enumerator.MoveNext())
                        {
                            TIObjectData data = CreateNewItem(enumerator);
                            if (Cache != null)
                                Cache.Add(data);
                            inStorage.Add(data.GlobalId, data);
                        }
                    }
                }
            }
            List<TIObjectData> list = ListUtil.Fusion<TIObjectData>(globalIds, inCache, inStorage); // Create the object list on the same order as the GlobalId list
            if ((mode & GetDataMode.Find) != GetDataMode.Find && list.Count != globalIds.Count())  
                throw new DataNotFoundException("GlobalIds", globalIds.ToString());
            return list;
        }



        public virtual void Persist(IEnumerable list)
        {
            if (list == null)
                throw new ArgumentNullException("list");
            bool empty = true;
            foreach (object data in list)
            {
                Persist((TIObjectData)data);
                if (empty == true)
                    empty = false;
            }
            if (empty)
                throw new ArgumentException("list is empty");
        }

        public virtual void Persist(TIObjectData data)
        {
            CallAXStaticMethod("Persist", CopyToContainer(data));
        }

        public virtual void Persist(IObjectData data)
        {
            Persist((TIObjectData)data);
        }

        public virtual TIObjectData PersistAndGet(TIObjectData data)
        {
            using (AxaptaContainer container = (AxaptaContainer)CallAXStaticMethod("PersistAndGet", CopyToContainer(data)))
            {
                IEnumerator enumerator = container.GetEnumerator();
                if (enumerator.MoveNext())
                {
                    CopyFromContainer(enumerator, data);
                    if (enumerator.MoveNext())
                        throw new ArgumentException("Container size error", "container");
                    if (Cache != null)
                        Cache.Add(data);
                    return data;
                }
                else
                    throw new DataNotFoundException();
            }
        }

        public void Dispose()
        {
        }

        #endregion

        #region Interface override
        IObjectData IObjectProvider.CreateNewItem()
        {
            return CreateNewItem();
        }

        IObjectData IObjectProvider.GetData(string globalId, GetDataMode mode)
        {
            return GetData(globalId, mode);
        }

        IEnumerable IObjectProvider.GetList(string[] globalIds, GetDataMode mode)
        {
            return GetList(globalIds, mode);
        }


        public virtual IObjectData PersistAndGet(IObjectData data)
        {
            return PersistAndGet((TIObjectData)data);
        }

        #endregion

        #region Public

        public static string GetGlobalId(Type providerType, int tableId, Int64 recordId)
        {
            if (recordId == 0 || tableId == 0)
                throw new ArgumentException("second and fird item of container can't be 0 valued");
            return providerType.Name + '@' + Convert.ToString(tableId, CultureInfo.InvariantCulture) + '.' + Convert.ToString(recordId, CultureInfo.InvariantCulture);
        }
        public static string GetDAXClassName(Type providerType)
        {
            return "PJK" + providerType.Name;
        }



        #endregion

        #region Protected

        protected TIObjectData CreateNewItem(IEnumerator container)
        {
            TIObjectData temp = (TIObjectData)CreateNewItem();
            CopyFromContainer(container, temp);
            return temp;
        }

        protected IObjectDataCache<TIObjectData> Cache
        {
            get { return _Cache; }
        }

        protected object CallAXStaticMethod(string methodName, object param)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param);
        }
        protected object CallAXStaticMethod(string methodName, object param1, object param2)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param1, param2);
        }
        protected object CallAXStaticMethod(string methodName, object param1, object param2, object param3)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param1, param2, param3);
        }
        protected object CallAXStaticMethod(string methodName, object[] param)
        {
            return DynamicsAXBroker.StaticMethod(GetDAXClassName(), methodName, param);
        }

        protected string GetGlobalId(int tableId, Int64 recordId)
        {
            return GetGlobalId(this.GetType(), tableId, recordId);
        }

        protected virtual void CopyFromContainer(IEnumerator enumerator, TIObjectData data)
        {
            if (enumerator == null)
                throw new ArgumentNullException("container");
            int tableId = (int)enumerator.Current;
            if (enumerator.MoveNext())
            {
                data.GlobalId = GetGlobalId(tableId, (Int64)enumerator.Current);
                return;
            }
            throw new Exception("the enumerator's list is empty or has the wrong number of items");
        }

        protected virtual AxaptaContainer CopyToContainer(TIObjectData data)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            AxaptaContainer container = DynamicsAXBroker.CreateContainer();
            try
            {
                int tableId;
                Int64 recordId;

                GetAxRecordInfo(data.GlobalId, out tableId, out recordId);
                container.Add(tableId);
                container.Add(recordId);
            }
            catch
            {
                container.Dispose();
                throw;
            }
            return container;
        }

        #endregion

        #region Private


        private string GetDAXClassName()
        {
            return GetDAXClassName(this.GetType());
        }

        private void GetAxRecordInfo(string globalId, out int tableId, out Int64 recordId)
        {
            if (globalId == null || globalId == "")
                throw new ArgumentNullException("globalId");
            string[] parts = globalId.Split("@.".ToCharArray(), 3);
            if (parts.Count() != 3)
                throw new ArgumentException("should be \"" + this.GetType().Name + "@AxTableId.AxRecordId\" : " + globalId, "globalId");
            if (parts[0] != this.GetType().Name)
                throw new ArgumentException(parts[0] + " found. Should be " + this.GetType().Name, "globalId");
            tableId = Convert.ToInt32(parts[1], CultureInfo.InvariantCulture);
            if (tableId == 0)
                throw new ArgumentOutOfRangeException("globalId", "extracted tableId is 0");
            recordId = Convert.ToInt64(parts[2], CultureInfo.InvariantCulture);
            if (recordId == 0L)
                throw new ArgumentOutOfRangeException("globalId", "extracted recordId is 0");
        }

        #endregion

        #region Fields

        private readonly IObjectDataCache<TIObjectData> _Cache;

        #endregion


    }
}
